#include "stdafx.h"
#include "Video.h"
#include "DepthStencil.h"
#include "ViewPort.h"

namespace Reign
{namespace Video
{namespace D3D10
{
	#pragma region Cap Properties
	bool Caps::D3D10_1::get() {return d3d10_1;}
	void Caps::D3D10_1::set(bool value) {d3d10_1 = value;}

	ShaderVersions Caps::MaxShaderVersion::get() {return maxShaderVersion;}
	void Caps::MaxShaderVersion::set(ShaderVersions value) {maxShaderVersion = value;}
	#pragma endregion

	#pragma region Properties
	ID3D10Device* Video::Device::get() {return device;}

	Reign::Video::D3D10::Caps^ Video::Caps::get() {return caps;}
	void Video::Caps::set(Reign::Video::D3D10::Caps^ value) {caps = value;}
	#pragma endregion

	#pragma region Constructors
	Video::Video(DisposableI^ parent, Window^ window, bool vSync)
	: Disposable(parent)
	{
		this->window = window;
		lastWindowFrameSize = window->FrameSize;
		init(parent, window->Handle, 0, 0, false, vSync);
	}

	Video::Video(DisposableI^ parent, IntPtr handle, int width, int height, bool fullscreen, bool vSync)
	: Disposable(parent)
	{
		init(parent, handle, width, height, fullscreen, vSync);
	}

	void Video::init(DisposableI^ parent, IntPtr handle, int width, int height, bool fullscreen, bool vSync)
	{
		null();
		try
		{
			caps = gcnew Reign::Video::D3D10::Caps();
			ShaderI::FileTag = L"D3D10_";

			currentVertexResourceCount = 8;
			currentPixelResourceCount = currentVertexResourceCount;
			currentVertexResources = new ID3D10ShaderResourceView*[currentVertexResourceCount];
			currentPixelResources = new ID3D10ShaderResourceView*[currentPixelResourceCount];
			for (int i = 0; i != currentVertexResourceCount; ++i)
			{
				currentVertexResources[i] = 0;
				currentPixelResources[i] = 0;
			}

			if (width == 0 || height == 0)
			{
				RECT rect;
				GetClientRect((HWND)handle.ToInt32(), &rect);
				width = rect.right - rect.left;
				height = rect.bottom - rect.top;
			}

			DXGI_SWAP_CHAIN_DESC swapChainDesc;
			ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));
			swapChainDesc.BufferCount = 1;
			swapChainDesc.BufferDesc.Width = width;
			swapChainDesc.BufferDesc.Height = height;
			swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
			swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
			swapChainDesc.OutputWindow = (HWND)handle.ToInt32();
			swapChainDesc.SampleDesc.Count = 1;
			swapChainDesc.SampleDesc.Quality = 0;
			swapChainDesc.Windowed = !fullscreen;

			UINT createDeviceFlags = 0;
			ID3D10Device1* device1TEMP;
			IDXGISwapChain* swapChainTEMP;
			if (FAILED(D3D10CreateDeviceAndSwapChain1(0, D3D10_DRIVER_TYPE_HARDWARE, 0, createDeviceFlags, D3D10_FEATURE_LEVEL_10_1, D3D10_1_SDK_VERSION, &swapChainDesc, &swapChainTEMP, &device1TEMP)))
			{
				ID3D10Device* deviceTEMP;
				if (FAILED(D3D10CreateDeviceAndSwapChain(0, D3D10_DRIVER_TYPE_HARDWARE, 0, createDeviceFlags, D3D10_SDK_VERSION, &swapChainDesc, &swapChainTEMP, &deviceTEMP)))
				{
					Debug::ThrowError(L"Video", L"Failed to create Device and SwapChain\nInstalling the 'DirectX End-User Runtime Web Installer' from Microsoft may fix the problem.");
				}
				device = deviceTEMP;
				Caps->D3D10_1 = false;
				Caps->MaxShaderVersion = ShaderVersions::HLSL_4_0;
			}
			else
			{
				ID3D10Device* deviceTEMP;
				device1TEMP->QueryInterface(__uuidof(ID3D10Device), (LPVOID*)&deviceTEMP);
				device = deviceTEMP;
				Caps->D3D10_1 = true;
				Caps->MaxShaderVersion = ShaderVersions::HLSL_4_1;
			}
			swapChain = swapChainTEMP;
			this->vSync = vSync ? 1 : 0;

			// create renderTarget and depthStencil views
			createViews(width, height);
		
			// Apply Defaults
			EnableRenderTarget();
		}
		catch (Exception^ ex)
		{
			delete this;
			throw ex;
		}
	}

	void Video::createViews(int width, int height)
	{
		// RenterTarget View
		ID3D10Texture2D* backBuffer = 0;
		if (FAILED(swapChain->GetBuffer(0, __uuidof(ID3D10Texture2D), (void**)&backBuffer)))
		{
			Debug::ThrowError(L"Video", L"Failed to get BackBuffer");
		}
		ID3D10RenderTargetView* renderTargetTEMP;
		if (FAILED(device->CreateRenderTargetView(backBuffer, 0, &renderTargetTEMP)))
		{
			Debug::ThrowError(L"Video", L"Failed to create RenderTargetView");
		}
		renderTarget = renderTargetTEMP;
		if (backBuffer) backBuffer->Release();
		
		// DepthStencil Texture
		D3D10_TEXTURE2D_DESC descDepth;
		ZeroMemory(&descDepth, sizeof(descDepth));
		descDepth.Width = width;
		descDepth.Height = height;
		descDepth.MipLevels = 1;
		descDepth.ArraySize = 1;
		descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;//DXGI_FORMAT_D32_FLOAT;
		descDepth.SampleDesc.Count = 1;
		descDepth.SampleDesc.Quality = 0;
		descDepth.Usage = D3D10_USAGE_DEFAULT;
		descDepth.BindFlags = D3D10_BIND_DEPTH_STENCIL;
		descDepth.CPUAccessFlags = 0;
		descDepth.MiscFlags = 0;
		ID3D10Texture2D* depthTextureTEMP = 0;
		if (FAILED(device->CreateTexture2D(&descDepth, 0, &depthTextureTEMP)))
		{
			Debug::ThrowError(L"Video", L"Failed to create DepthStencil texture");
		}
		depthTexture = depthTextureTEMP;
		
		// DepthStencil View
		D3D10_DEPTH_STENCIL_VIEW_DESC descDSV;
		ZeroMemory(&descDSV, sizeof(descDSV));
		descDSV.Format = descDepth.Format;
		if (descDepth.SampleDesc.Count > 1) descDSV.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2DMS;
		else descDSV.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2D;
		descDSV.Texture2D.MipSlice = 0;
		ID3D10DepthStencilView* depthStencilTEMP;
		if (FAILED(device->CreateDepthStencilView(depthTexture, &descDSV, &depthStencilTEMP)))
		{
			if (depthTexture) depthTexture->Release();
			Debug::ThrowError(L"Video", L"Failed to create DepthStencilView");
		}
		depthStencil = depthStencilTEMP;
		if (depthTexture) depthTexture->Release();
	}

	Video::~Video()
	{
		disposeChilderen();
		if (currentVertexResources) delete currentVertexResources;
		if (currentPixelResources) delete currentPixelResources;
		if (device) device->ClearState();
		if (renderTarget) renderTarget->Release();
		if (depthStencil) depthStencil->Release();
		if (depthTexture) depthTexture->Release();
		if (swapChain) swapChain->Release();
		if (device) device->Release();
		null();
	}

	void Video::null()
	{
		currentVertexResources = 0;
		currentPixelResources = 0;
		renderTarget = 0;
		depthStencil = 0;
		depthTexture = 0;
		swapChain = 0;
		device = 0;
	}
	#pragma endregion

	#pragma region Methods
	void Video::Update()
	{
		if (window == nullptr) return;

		I32::Vector2 frame = window->FrameSize;
		if (lastWindowFrameSize != frame && frame.X != 0 && frame.Y != 0)
		{
			renderTarget->Release();
			depthStencil->Release();
			depthTexture->Release();
			swapChain->ResizeBuffers(1, frame.X, frame.Y, DXGI_FORMAT_R8G8B8A8_UNORM, 0);
			createViews(frame.X, frame.Y);
			lastWindowFrameSize = frame;
		}
	}

	void Video::EnableRenderTarget()
	{
		ID3D10RenderTargetView* renderTargetTEMP = renderTarget;
		device->OMSetRenderTargets(1, &renderTargetTEMP, depthStencil);
		currentRenderTarget = renderTarget;
		currentDepthStencil = depthStencil;
	}

	void Video::EnableRenderTarget(DepthStencilI^ depthStencil)
	{
		ID3D10RenderTargetView* renderTargetTEMP = renderTarget;
		currentRenderTarget = renderTarget;

		if (depthStencil != nullptr)
		{
			ID3D10DepthStencilView* surface = ((DepthStencil^)depthStencil)->Surface;
			currentDepthStencil = surface;
			device->OMSetRenderTargets(1, &renderTargetTEMP, surface);
		}
		else
		{
			device->OMSetRenderTargets(1, &renderTargetTEMP, 0);
		}
	}

	void Video::DisableRenderTarget()
	{
		device->OMSetRenderTargets(0, 0, 0);
	}

	void Video::Present()
	{
		swapChain->Present(vSync, 0);
	}
	
	void Video::Clear(float r, float g, float b, float a)
	{
		float clearColor[4] = {r, g, b, a};
		device->ClearRenderTargetView(currentRenderTarget, clearColor);
		device->ClearDepthStencilView(currentDepthStencil, D3D10_CLEAR_DEPTH | D3D10_CLEAR_STENCIL, 1.0f, 0);
	}

	void Video::ClearColor(float r, float g, float b, float a)
	{
		float clearColor[4] = {r, g, b, a};
		device->ClearRenderTargetView(currentRenderTarget, clearColor);
	}

	void Video::ClearDepthStencil()
	{
		device->ClearDepthStencilView(currentDepthStencil, D3D10_CLEAR_DEPTH | D3D10_CLEAR_STENCIL, 1.0f, 0);
	}

	void Video::removeActiveResource(ID3D10ShaderResourceView* shaderResource)
	{
		if (currentVertexResourceCount != 0)
		{
			bool found = false;
			for (int i = 0; i != currentVertexResourceCount; ++i)
			{
				if (currentVertexResources[i] == shaderResource)
				{
					currentVertexResources[i] = 0;
					found = true;
				}
			}
			if(found) device->VSSetShaderResources(0, currentVertexResourceCount, currentVertexResources);
		}

		if (currentPixelResourceCount != 0)
		{
			bool found = false;
			for (int i = 0; i != currentPixelResourceCount; ++i)
			{
				if (currentPixelResources[i] == shaderResource)
				{
					currentPixelResources[i] = 0;
					found = true;
				}
			}
			if (found) device->PSSetShaderResources(0, currentPixelResourceCount, currentPixelResources);
		}
	}

	DXGI_FORMAT Video::surfaceFormat(SurfaceFormats surfaceFormat)
	{
		switch (surfaceFormat)
		{
			case (SurfaceFormats::RGBAx8): return DXGI_FORMAT_R8G8B8A8_UNORM;
			case (SurfaceFormats::RGBx10_Ax2): return DXGI_FORMAT_R10G10B10A2_UNORM;
			case (SurfaceFormats::RGBAx16f): return DXGI_FORMAT_R16G16B16A16_FLOAT;
			case (SurfaceFormats::RGBAx32f): return DXGI_FORMAT_R32G32B32A32_FLOAT;
			default:
				Debug::ThrowError(L"Video", L"Unsuported SurfaceFormat.");
				return DXGI_FORMAT_R8G8B8A8_UNORM;
		}
	}
	#pragma endregion
}
}
}